from functools import wraps
from typing import Any, Dict

import httpx
from httpx import Response
from pydantic import BaseModel

from app.constants import AUTHORIZATION, HEADER_TRACE_ID, SAME_TOKEN
from app.core.log import logger
from app.core.nacos import nacos_config
from app.enums.method import Method
from app.exception import BusinessException
from app.utils.context import get_token, get_trace_id, get_same_token


class Interceptor:
    """ 请求拦截器 """

    def pre_handle(self, method: Method, headers: Dict[str, str], params: dict, body: dict):
        """
        前置处理

        :param method: HTTP方法
        :param headers: 请求头字典
        :param params: 查询参数字典
        :param body: 请求体字典
        """
        pass

    def post_handle(self, response: Response | None):
        """
        后置处理

        :param response: HTTP响应对象
        :return: 处理后的响应结果
        """
        pass


class DefaultInterceptor(Interceptor):
    """ 默认的请求拦截器 """

    def pre_handle(self, method: Method, headers: Dict[str, str], params: dict, body: dict):
        """
        默认的请求 前置处理

        :param method: HTTP方法
        :param headers: 请求头字典
        :param params: 查询参数字典
        :param body: 请求体字典
        """
        if method == Method.POST or method == Method.PUT:
            headers["Content-Type"] = "application/json"
        headers[AUTHORIZATION] = get_token() or ""
        headers[HEADER_TRACE_ID] = get_trace_id() or ""
        headers[SAME_TOKEN] = get_same_token() or ""

    def post_handle(self, response: Response | None) -> Any:
        """
        默认的请求 后置处理

        :param response: HTTP响应对象
        :return: 处理后的响应结果
        """
        if response is None:
            return None

        if response.status_code != 200:
            raise BusinessException(response.text)

        if response.text:
            return response.json()
        else:
            return response.text


class ExceptionHandler:
    """ 异常处理 """

    def handle(self, e: Exception):
        """
        处理异常

        :param e: 异常对象
        """
        pass


class DefaultExceptionHandler(ExceptionHandler):
    """ 默认的异常处理 """

    def handle(self, e: Exception):
        """
        默认的异常处理

        :param e: 异常对象
        """
        logger.exception("远程调用发生错误", e)


class RemoteApi:
    """
    远程API调用装饰器类

    :param method: HTTP请求方法
    :param service_name: 服务名称
    :param path: 请求路径
    :param interceptor: 请求拦截器
    :param exception_handler: 异常处理器
    """

    def __init__(self,
                 method: Method,
                 service_name: str,
                 path: str,
                 interceptor: Interceptor | None = None,
                 exception_handler: ExceptionHandler | None = None):

        self.method = method
        self.service_name = service_name
        self.path = path
        # 设置拦截器
        self.interceptor = interceptor
        if self.interceptor is None:
            self.interceptor = DefaultInterceptor()
        # 设置异常处理
        self.exception_handler = exception_handler
        if self.exception_handler is None:
            self.exception_handler = DefaultExceptionHandler()

    def __call__(self, func):
        """
        函数调用时触发

        :param func: 被装饰的函数
        :return: 包装后的函数
        """

        @wraps(func)
        async def wrapper(*args, **kwargs):
            """
            包装函数，实现远程API调用逻辑

            :param args: 位置参数
            :param kwargs: 关键字参数
            :return: 远程调用结果
            """
            headers: Dict[str, str] = {}
            params = {}
            body = {}
            try:
                # 获取参数
                if self.method == Method.GET or self.method == Method.DELETE:
                    # 优先使用kwargs作为查询参数
                    params.update(kwargs)

                    # 如果有位置参数，尝试映射到函数参数名
                    if len(args) > 0:
                        var_names = func.__code__.co_varnames[:len(args)]
                        for index, pname in enumerate(var_names):
                            # 避免覆盖已有的关键字参数
                            if pname in kwargs:
                                continue
                            params[pname] = args[index]
                elif self.method == Method.POST or self.method == Method.PUT:
                    if len(args) > 0:
                        # 添加类型检查
                        if not isinstance(args[0], BaseModel):
                            raise BusinessException("参数必须是Pydantic模型类的实例")
                        body = args[0].model_dump(mode='json', by_alias=True)
                else:
                    raise BusinessException("不支持的请求方式！")

                # 从注册中心获取服务地址
                service_ip = await nacos_config.get_server_ip_address(self.service_name)

                # 请求拦截器-前置处理
                self.interceptor.pre_handle(self.method, headers, params, body)

                # 发起请求
                async with httpx.AsyncClient() as client:
                    response = await client.request(
                        method=self.method.name,
                        url=f"{service_ip}{self.path}",
                        params=params,
                        json=body,
                        headers=headers
                    )

                # 请求拦截器-后置处理
                return self.interceptor.post_handle(response)
            except Exception as e:
                # 异常处理
                if self.exception_handler:
                    self.exception_handler.handle(e)
                return None

        return wrapper
